package com.urbanski.clipboard.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.urbanski.clipboard.common.message.CreateResponse;
import com.urbanski.clipboard.common.message.CreateResponse.CreateResponseValue;
import com.urbanski.clipboard.common.util.CommonConstants;
import com.urbanski.clipboard.server.session.ServerSession;
import com.urbanski.util.logging.LoggingUtil;

public class ClipboardServer
{
	private static final Logger LOGGER = Logger.getLogger(ClipboardServer.class);

	private static int port = 0;
	private static ServerSocket serverSocket;
	private static boolean stop = false;

	private static ServerCommandLineListener commandLineListener;
	private static Map<String, ClientSocket> clients = new HashMap<>();
	private static Map<String, ServerSession> sessions = new HashMap<>();

	public static void main(String[] args)
	{
		args = StringUtils.split("80");
		LOGGER.trace("Entering ClipboardServer.main");

		if (!init(args))
		{
			return;
		}

		try
		{
			serverSocket = new ServerSocket(port);
		}
		catch (IOException e)
		{
			LoggingUtil.logAndPrintError(LOGGER, "Error opening server", e);
		}

		if (serverSocket != null)
		{
			commandLineListener = new ServerCommandLineListener();
			Thread commandLineListenerThread = new Thread(commandLineListener);
			commandLineListenerThread.start();

			while (!stop)
			{
				Socket client = null;
				try
				{
					LoggingUtil.logAndPrintInfo(LOGGER, "Server waiting for connection");
					client = serverSocket.accept();
				}
				catch (IOException e)
				{
					if (!stop)
					{
						LoggingUtil.logAndPrintError(LOGGER, "Error occurred while waiting for connections", e);
					}
				}

				if (client != null)
				{
					ObjectInputStream inputStream;
					try
					{
						inputStream = new ObjectInputStream(client.getInputStream());
						String name = (String)inputStream.readObject();
						System.out.println("Accepted connection from " + name);
						ClientSocket clientSocket = new ClientSocket(client, name, inputStream);
						clients.put(name, clientSocket);
						new Thread(new SocketListener(clientSocket)).start();
					}
					catch (IOException | ClassNotFoundException e)
					{
						LoggingUtil.logAndPrintError(LOGGER, "Error while accepting client connection", e);
					}
				}
			}
		}

		LOGGER.trace("Exiting ClipboardServer.main");
	}

	public static void stop()
	{
		LOGGER.trace("Entering ClipboardServer.stop");

		stop = true;
		if (serverSocket != null)
		{
			try
			{
				LoggingUtil.logAndPrintInfo(LOGGER, "Shutting down server");
				serverSocket.close();
				LoggingUtil.logAndPrintInfo(LOGGER, "Server stopped listening");
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}

		LOGGER.trace("Exiting ClipboardServer.enclosing_method");
	}

	public static void removeClient(ClientSocket client)
	{
		synchronized (sessions)
		{
			Set<String> emptySessions = new HashSet<>();

			for (ServerSession session : sessions.values())
			{
				session.removeClient(client.getClientName());
				if (session.getClients().size() == 0)
				{
					emptySessions.add(session.getSessionName());
				}
			}

			for (String s : emptySessions)
			{
				sessions.remove(s);
			}
		}

		if (client != null)
		{
			synchronized (clients)
			{
				clients.remove(client.getClientName());
			}
			client.close();
		}
	}

	public static void removeSession(String sessionName)
	{
		synchronized (sessions)
		{
			sessions.remove(sessionName);
		}
	}

	public static void removeClient(String clientName)
	{
		ClientSocket client = clients.get(clientName);

		if (client != null)
		{
			synchronized (clients)
			{
				clients.remove(client.getClientName());
			}
			client.close();
		}
	}

	public static CreateResponse createSession(ServerSession session)
	{
		synchronized (sessions)
		{
			if (sessions.containsKey(session.getSessionName()))
			{
				return new CreateResponse(CreateResponseValue.SESSION_ALREADY_EXISTS);
			}
			else
			{
				sessions.put(session.getSessionName(), session);
				return new CreateResponse(CreateResponseValue.SUCCESS);
			}
		}
	}

	public static ServerSession getSession(String name)
	{
		synchronized (sessions)
		{
			return sessions.get(name);
		}
	}

	public static void printInfo()
	{
		System.out.println("Sessions:\n");
		for (ServerSession session : sessions.values())
		{
			System.out.println(session + "\n");
		}

		System.out.println("Clients:\n");
		for (ClientSocket client : clients.values())
		{
			System.out.println(client + CommonConstants.NEWLINE);
		}
	}

	public static Collection<ServerSession> getSessionsForClient(String clientName)
	{
		synchronized (sessions)
		{
			Set<ServerSession> set = new HashSet<>();
			for (ServerSession s : sessions.values())
			{
				if (s.clientExists(clientName))
				{
					set.add(s);
				}
			}
			
			return set;
		}
	}

	private static boolean init(String[] args)
	{
		LOGGER.trace("Entering ClipboardServer.init");

		boolean success = processArgs(args);

		LOGGER.trace("Exiting ClipboardServer.init");
		return success;
	}

	private static boolean processArgs(String[] args)
	{
		LOGGER.trace("Entering ClipboardServer.processArgs");

		boolean error = false;

		if (args.length != 1)
		{
			LoggingUtil.logAndPrintError(LOGGER, "Invalid number of arguments specified");
			error = true;
		}
		else
		{
			try
			{
				port = Integer.parseInt(args[0]);
			}
			catch (NumberFormatException e)
			{
				LoggingUtil.logAndPrintError(LOGGER, "Invalid port specified: " + args[0]);
				error = true;
			}
		}

		LOGGER.trace("Exiting ClipboardServer.processArgs");
		return !error;
	}
}
